/**
 * 4.类
 * !表示此值非空
 */

/**
 * 一、TS中定义类
 */
class Pointer {
    x!:number; // 实例上的属性必须先声明
    y!:number;

    constructor(x: number, y?: number, ...args: number[]) {
        this.x = x;
        // 断言 y 为 number
        this.y = y as number;
    }
}
let p = new Pointer(100, 200);
// 实例上的属性需要先声明在使用，构造函数中的参数可以使用可选参数和剩余参数

/**
 * 二、类中的修饰符
 * public 修饰符（谁都可以访问到）
 */
class Animal {
    public name!: string; // 不写public 默认也是公开的
    public age!: number;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}
// 子类继承 Animal
class Cat extends Animal {
    constructor(name: string, age: number) {
        super(name, age);
        // console.log(this.name, this.age);// 子类访问
    }
}
let cat = new Cat("Tom", 18);
// console.log(cat.name, cat.age);// 外层访问
// 我们可以通过参数属性来简化父类中的代码
// class Animal {
//     constructor(public name: string, public age: number) {
//         this.name = name;
//         this.age = age;
//     }
// }

// protected 修饰符（自己和子类可以访问）
class Animal2 {
    constructor(protected name: string, protected age: number) {
        this.name = name;
        this.age = age;
    }
}
class Cat2 extends Animal2 {
    constructor(name: string, age: number) {
        super(name, age);
        // console.log(this.name, this.age);
    }
}
let p3 = new Cat2("Tom", 18);
// console.log(p3.name, p3.age); // 无法访问

// private 修饰符（除了自己都访问不到）
class Animal3 {
    constructor(private name: string, private age: number) {
        this.name = name;
        this.age = age;
    }
}
class Cat3 extends Animal3 {
    constructor(name: string, age: number) {
        super(name, age);
        // console.log(this.name, this.age); // 无法访问
    }
}
let p4 = new Cat3("Tom", 18);
// console.log(p3.name, p3.age); // 无法访问

// readonly 修饰符（仅读修饰符）
class Animal4 {
    constructor(public readonly name: string, public age: number) {
        this.name = name;
        this.age = age;
    }
    changeName(name: string) {
        // 仅读属性只能在 constructor 中被赋值
        // this.name = name;
    }
}
class Cat4 extends Animal4 {
    constructor(name: string, age: number) {
        super(name, age);
    }
}
let p5 = new Cat4("Tom", 18);
p5.changeName("dada");


/**
 * 三、静态属性和方法
 */
class Animal6 {
    static type = "小姐姐"; // 静态属性
    static getName() { // 静态方法
        return "按摩"
    }
    private _name: string = "Tmi";

    get name() { // 属性访问器
        return this._name;
    }
    set name(name: string) {
        this._name = name;
    }
}
let animal = new Animal6();
// console.log(animal.name);
// 静态属性和静态方法是可以被子类所继承的

/**
 * 四、Super属性
 */
class Animal7 {
    say(message: string) {
        // console.log(message)
    }
    static getType() {
        return "按摩"
    }
}
class Cat6 extends Animal7 {
    // 原型方法中的super指代的是父类的原型
    say() {
        super.say("会按摩");
    }
    // 静态方法中的super指代的是父类
    static getType() {
        return super.getType();
    }
}
let cat6 = new Cat6();
// console.log(Cat6.getType())

/**
 * 五、类的装饰器
 */
// 1、装饰类
function addSay(target: any) {
    target.prototype.say = function () {
        // console.log("say")
    }
}
@addSay
class Person {
    say!: Function
}
let person = new Person();
person.say();
// 装饰类可以给类扩展功能，需要开启 experimentalDecorators: true

// 2、装饰类中属性
function toUpperCase(target: any, key: string) {
    let value = target[key];
    Object.defineProperty(target, key, {
        get() {
            return value.toUpperCase();
        },
        set(newValue) {
            value = newValue;
        }
    })
}
function double(target: any, key: string) {
    let value = target[key];
    Object.defineProperty(target, key, {
        get() {
            return value * 2;
        },
        set(newValue) {
            value = newValue
        }
    })
}
class Person2 {
    @toUpperCase
    name: string = "Dada";
    @double
    static age: number = 10;
    getName() {
        return this.name;
    }
}
let person2 = new Person2();
// console.log(person2.getName(), Person2.age)
// 装饰属性可以对属性的内容进行改写，装饰的是实例属性则target指向类的原型
// 装饰的是静态属性则 target 执行类本身

// 3、装饰类中方法
function noEnum(target: any, key: string, descriptor: PropertyDescriptor) {
    console.log(descriptor);
    descriptor.enumerable = false;
}
class Person3 {
    @toUpperCase
    name: string = "大大";
    @double
    static age: number = 10;
    @noEnum
    getName() {
        return this.name;
    }
}
let person3 = new Person3();
// console.log(person3); // getName 不可枚举

// 4、装饰参数
function addPrefix(target: any, key: string, paramIndex: number) {
    console.log(target, key, paramIndex); // Person.prototype.getName 0
}
class Person4 {
    @toUpperCase
    name: string = "daa";
    @double
    static age: number = 10;
    prefix!: string;
    @noEnum
    getName(@addPrefix prefix: string) {
        return this.name;
    }
}

/**
 * 六、抽象类
 * 抽象类无法被实例化，只能被继承，抽象发放不能再抽象类中实现，只能在抽象类的具体子类中实现，而且必须实现
 */
abstract class Animal1 {
    name!: string;
    abstract speak(): void;
}
class Cat0 extends Animal1 {
    speak() {
        console.log("按摩")
    }
}
class Dog extends Animal1 {
    speak(): string {
        console.log("汪汪叫");
        return "wangwang";
    }
}
// 定义类型时 void 表示函数的返回值为空（不关心返回值类型，所有在定义函数时也不关心函数返回值类型）
